/*
 * Copyright (c) 2018 - 2028. www.vtuzx.com Inc. All Rights Reserved
 */
package com.vtuzx.kingdee.xqc_core.service.impl;

import com.vtuzx.core.VtuzxException;
import com.vtuzx.core.bean.dto.VtuzxMap;
import com.vtuzx.core.constant.IVtuzxConst;
import com.vtuzx.core.utils.VtuzxUtil;
import com.vtuzx.kingdee.xqc_core.bean.VtuzxArrayList;
import com.vtuzx.kingdee.xqc_core.bean.xqc.BillMaterial;
import com.vtuzx.kingdee.xqc_core.bean.xqc.BillMaterialBox;
import com.vtuzx.kingdee.xqc_core.constant.*;
import com.vtuzx.kingdee.xqc_core.dao.*;
import com.vtuzx.kingdee.xqc_core.service.IVtuzxCoreInventoryService;
import org.bson.Document;
import org.bson.types.ObjectId;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.mongodb.core.query.Criteria;
import org.springframework.stereotype.Service;

import java.math.BigDecimal;
import java.util.*;

/**
 * 即时库存服务
 */
@Service
public class XqcCoreInventoryServiceImpl implements IVtuzxCoreInventoryService {

    /**
     * Logger
     */
    private static final Logger _logger = LoggerFactory.getLogger(VtuzxCoreServiceImpl.class);

    /**
     * 即时库存Dao
     */
    @Autowired
    private InventoryDao inventoryDao;
    /**
     * 即时库存分录Dao
     */
    @Autowired
    private InventoryDetailDao inventoryDetailDao;

    /**
     * 即时库存履历Dao
     */
    @Autowired
    private InventoryHistoryDao inventoryHistoryDao;

    /**
     * 即时库存履历分录Dao
     */
    @Autowired
    private InventoryHistoryDetailDao inventoryHistoryDetailDao;

    @Autowired
    private CoreParamDao CoreParamDao;

    @Override
    public void reduceInventory(ObjectId _userId, String userName, BillEvent billEvent, BillType billType, String stockNo, String billNo, List<String> boxNoList, List<VtuzxMap> bcBoxList) throws VtuzxException {
        _logger.info(String.format("即时库存出库开始: 事件[%s], 单据类型[%s], 单号[%s]", billEvent, billType.label(), billNo));
        int sign = billType.inventoryIO();
        if (sign == 0) {
            throw new VtuzxException(IXqcConst.Msg_E_XQC_stock_out_bill_failure, billNo, billType.label(), billEvent.label());
        }
        switch (billEvent) {
            case Audit: {
                if (sign > 0) {
                    throw new VtuzxException(IXqcConst.Msg_E_XQC_stock_out_event_failure, billNo, billType.label(), billEvent.label());
                }
                break;
            }
            case Unaudit: {
                if (sign < 0) {
                    throw new VtuzxException(IXqcConst.Msg_E_XQC_stock_out_event_failure, billNo, billType.label(), billEvent.label());
                }
                break;
            }
            default: {
                throw new VtuzxException(IXqcConst.Msg_E_XQC_stock_out_event_failure, billNo, billType.label(), billEvent.label());
            }
        }
        String transactionKey = IXqcConst.getTransactionKey();
        _logger.info(String.format("库存增加事务启动: 事件[%s], 单据类型[%s], 单号[%s]", billEvent.label(), billType.label(), billNo));
        Map<ObjectId, Document> updateInventoryMap = new HashMap<>();
        Map<ObjectId, Document> updatedInventoryMap = new HashMap<>();
        try {
            if (!VtuzxUtil.isEmpty(boxNoList)) {
                Criteria updCond = Criteria.where("boxNo").in(boxNoList).and("stockNo").is(stockNo).and("status").is(StockStatus.In.toString());
                Iterator<Document> detailItr = inventoryDetailDao.findList(updCond, null, 0, 0);
                while (detailItr.hasNext()) {
                    Document detail = detailItr.next();
                    ObjectId _inventoryId = detail.getObjectId("_inventoryId");
                    ObjectId _id = detail.getObjectId("_id");
                    double count = VtuzxUtil.getDouble(detail, "count");
                    boolean isTaskIn = VtuzxUtil.getBoolean(detail, "isTaskIn");
                    List<Document> traceBackDocList = new ArrayList<>();
                    if (isTaskIn) {
                        List<Document> traceBack = VtuzxUtil.getObject(detail, "traceBack");
                        List<String> traceBackNoList = new ArrayList<>();
                        if (!VtuzxUtil.isEmpty(traceBack)) {
                            for (Document traceMap: traceBack) {
                                String traceBackNo = VtuzxUtil.getString(traceMap, "traceBackNo");
                                String status = VtuzxUtil.getString(traceMap, "status");
                                if ("in".equals(status)) {
                                    traceBackNoList.add(traceBackNo);
                                    Document data = new Document("traceBackNo", traceBackNo).append("status", "out");
                                    traceBackDocList.add(data);
                                } else {
                                    traceBackDocList.add(traceMap);
                                }
                            }
                        }
                        detail.append("traceBack", traceBackNoList);
                    }
                    Document inventory = updateInventoryMap.get(_inventoryId);
                    if (inventory == null) {
                        inventory = new Document("boxCount", -1).append("count", -count).append("boxList", new ArrayList<>());
                        updateInventoryMap.put(_inventoryId, inventory);
                    } else {
                        inventory.append("boxCount", VtuzxUtil.getInt(inventory, "boxCount") - 1).append("count", VtuzxUtil.getDouble(inventory, "count") - count);
                    }
                    List<Document> boxList = VtuzxUtil.getObject(inventory, "boxList");
                    boxList.add(detail);

                    Document updateDoc = new Document("status", StockStatus.Out.toString());
                    if (isTaskIn) {
                        updateDoc.append("traceBack", traceBackDocList);
                    }
                    inventoryDetailDao.updateById(_id, updateDoc);
                }
            }
            if (!VtuzxUtil.isEmpty(bcBoxList)) {
//                Criteria updCond = Criteria.where("boxNo").in(boxNoList).and("stockNo").is(stockNo).and("status").is(StockStatus.In.toString());
//                UpdateResult updRes = inventoryDetailDao.updateMany(updCond, new Document("status", StockStatus.Out.toString()));
//                if (updRes.getModifiedCount() != boxNoList.size()) {
//                    throw new VtuzxException(IXqcConst.Msg_E_XQC_stock_out_exist);
//                }
                List<String> bcBoxNoList = new ArrayList<>();
                List<String> traceBackList = new ArrayList<>();
                List<String> bcLocationNoList = new ArrayList<>();
                VtuzxMap bcBoxNoMap = new VtuzxMap();
                for (VtuzxMap bcBox : bcBoxList) {
                    String boxNo = VtuzxUtil.getString(bcBox, "boxNo");
                    String locationNo = VtuzxUtil.getString(bcBox, "locationNo");
                    double count = VtuzxUtil.getDouble(bcBox, "count");
                    List<String> traceBack = VtuzxUtil.getObject(bcBox, "traceBack");
                    if (!VtuzxUtil.isEmpty(traceBack)) {
                        traceBackList.addAll(traceBack);
                    }
                    bcBoxNoList.add(boxNo);
                    bcLocationNoList.add(locationNo);
                    bcBoxNoMap.append(boxNo, count);
                }
                Criteria cond = new Criteria("boxNo").in(bcBoxNoList).and("stockNo").is(stockNo).and("locationNo").in(bcLocationNoList);
                if (traceBackList.size() != 0) {
                    cond.and("traceBack.traceBackNo").in(traceBackList);
                }
                Iterator<Document> detailItr = inventoryDetailDao.findList(cond, null, 0, 0);
                while (detailItr.hasNext()) {
                    Document detail = detailItr.next();
                    ObjectId _id = VtuzxUtil.getObject(detail, IVtuzxConst.Key__id);
                    ObjectId _inventoryId = detail.getObjectId("_inventoryId");
                    List<Document> traceBackMapList = new ArrayList<>();
                    List<String> traceBackNoList = new ArrayList<>();
                    if (traceBackList.size() != 0) {
                        List<Document> traceBack = VtuzxUtil.getObject(detail, "traceBack");
                        for (Document traceMap: traceBack) {
                            String traceBackNo = VtuzxUtil.getString(traceMap, "traceBackNo");
                            if (traceBackList.contains(traceBackNo)) {
                                Document data = new Document("traceBackNo", traceBackNo).append("status", "out");
                                traceBackNoList.add(traceBackNo);
                                traceBackMapList.add(data);
                            } else {
                                traceBackMapList.add(traceMap);
                            }
                        }
                        detail.append("traceBack", traceBackNoList);
                    }
                    double count = VtuzxUtil.getDouble(bcBoxNoMap, VtuzxUtil.getString(detail, "boxNo"));
                    Document inventory = updateInventoryMap.get(_inventoryId);
                    if (inventory == null) {
                        inventory = new Document("count", -count).append("boxList", new ArrayList<>());
                        updateInventoryMap.put(_inventoryId, inventory);
                    } else {
                        inventory.append("count", VtuzxUtil.getDouble(inventory, "count") - count);
                    }
                    List<Document> boxList = VtuzxUtil.getObject(inventory, "boxList");
                    boxList.add(detail);
                    Document decDoc = new Document();
                    decDoc.append("count", -1 * count);
                    Document updateDoc = new Document("$inc", decDoc);
                    if (traceBackList.size() != 0) {
                        updateDoc.append("$set", new Document("traceBack", traceBackMapList));
                    }
                    inventoryDetailDao.updateByIdRedirect(_id, updateDoc);
                }
            }
            for (ObjectId _inventoryId : updateInventoryMap.keySet()) {
                reduceInventoryMaterial(_userId, userName, transactionKey, stockNo, billEvent, billType, billNo, _inventoryId, updateInventoryMap.get(_inventoryId), updatedInventoryMap);
            }
            _logger.info(String.format("库存增加事务完成: 事件[%s], 单据类型[%s], 单号[%s]", billEvent.label(), billType.label(), billNo));
        } catch (Exception e) {
            _logger.warn(String.format("库存增加异常: 事件[%s], 单据类型[%s], 单号[%s], %s", billEvent.label(), billType.label(), billNo, e.getMessage()), e);
            if (!VtuzxUtil.isEmpty(boxNoList)) {
                Criteria updCond = Criteria.where("boxNo").in(boxNoList).and("stockNo").is(stockNo);
                inventoryDetailDao.updateMany(updCond, new Document("status", StockStatus.In.toString()));
            }
            Criteria rollbackCond = Criteria.where("transactionKey").is(transactionKey);
            inventoryHistoryDao.deleteMany(rollbackCond);
            inventoryHistoryDetailDao.deleteMany(rollbackCond);
            for (ObjectId _updateId : updatedInventoryMap.keySet()) {
                Document incDoc = updatedInventoryMap.get(_updateId);
                incDoc.append("boxCount", -1 * VtuzxUtil.getInt(incDoc, "boxCount"));
                incDoc.append("count", -1 * VtuzxUtil.getDouble(incDoc, "count"));
                inventoryDao.updateByIdRedirect(_updateId, new Document("$inc", incDoc));
                Document decDoc = new Document();
                decDoc.append("count", VtuzxUtil.getDouble(incDoc, "count"));
                inventoryDetailDao.updateOneRedirect(Criteria.where("_inventoryId").is(_updateId), new Document("$inc", decDoc));
            }
            _logger.info(String.format("库存增加回滚完成: 事件[%s], 单据类型[%s], 单号[%s]", billEvent.label(), billType.label(), billNo));
            throw e;
        }
    }

    /**
     * 保存库存物料
     *
     * @param _userId             用户ID
     * @param userName            用户名
     * @param stockNo             仓库编码
     * @param event               单据动作
     * @param billType            单据类型
     * @param billNo              单据号
     * @param _inventoryId        库存ID
     * @param countDoc            库存数量
     * @param updatedInventoryMap 更新库存数量
     */
    private void reduceInventoryMaterial(ObjectId _userId, String userName,
                                         String transactionKey, String stockNo, BillEvent event, BillType billType, String billNo,
                                         ObjectId _inventoryId, Document countDoc, Map<ObjectId, Document> updatedInventoryMap) {

        List<Document> boxList = VtuzxUtil.removeObject(countDoc, "boxList");

        Document inventoryDoc = inventoryDao.findById(_inventoryId);
        Document inventoryHistory = new Document("transactionKey", transactionKey)
                .append("_inventoryId", _inventoryId)
                .append("billNo", billNo)
                .append("billType", billType.toString())
                .append("billEvent", event.toString())
                .append("stockNo", stockNo)
                .append("materialNo", VtuzxUtil.getObject(inventoryDoc, "materialNo"))
                .append("batchNo", VtuzxUtil.getObject(inventoryDoc, "batchNo"))
                .append("produceDate", VtuzxUtil.getObject(inventoryDoc, "produceDate"))
                .append("exp", VtuzxUtil.getObject(inventoryDoc, "exp"))
                .append("unit", VtuzxUtil.getObject(inventoryDoc, "unit"))
                .append("boxCount", VtuzxUtil.getObject(countDoc, "boxCount"))
                .append("count", VtuzxUtil.getObject(countDoc, "count"));
        inventoryDao.updateStock(_userId, userName, _inventoryId, countDoc);

        updatedInventoryMap.put(_inventoryId, countDoc);
        ObjectId _historyId = inventoryHistoryDao.insertHistory(_userId, userName, inventoryHistory);

        List<Document> historyDetailDocList = new ArrayList<>();
        for (Document box : boxList) {
            Document historyDetailDoc = new Document("transactionKey", transactionKey)
                    .append("boxNo", VtuzxUtil.getString(box, "boxNo"))
                    .append("count", VtuzxUtil.getDouble(box, "count") * -1)
                    .append("stockNo", stockNo)
                    .append("locationNo", VtuzxUtil.getString(box, "locationNo"))
                    .append("status", StockStatus.Out.toString())
                    .append("_historyId", _historyId)
                    .append("_inventoryId", _inventoryId)
                    .append("traceBack", VtuzxUtil.getObject(box, "traceBack"));
            historyDetailDocList.add(historyDetailDoc);
        }
        inventoryHistoryDetailDao.insertDetail(_userId, userName, historyDetailDocList);
    }


    @Override
    public void reduceInventory(ObjectId _userId, String userName, BillEvent billEvent, BillType billType, String stockNo, String billNo, List<String> boxNoList, List<VtuzxMap> bcBoxList, List<Document> zsBoxList) throws VtuzxException {
        _logger.info(String.format("即时库存出库开始: 事件[%s], 单据类型[%s], 单号[%s]", billEvent, billType.label(), billNo));
        int sign = billType.inventoryIO();
        if (sign == 0) {
            throw new VtuzxException(IXqcConst.Msg_E_XQC_stock_out_bill_failure, billNo, billType.label(), billEvent.label());
        }
        switch (billEvent) {
            case Audit: {
                if (sign > 0) {
                    throw new VtuzxException(IXqcConst.Msg_E_XQC_stock_out_event_failure, billNo, billType.label(), billEvent.label());
                }
                break;
            }
            case Unaudit: {
                if (sign < 0) {
                    throw new VtuzxException(IXqcConst.Msg_E_XQC_stock_out_event_failure, billNo, billType.label(), billEvent.label());
                }
                break;
            }
            default: {
                throw new VtuzxException(IXqcConst.Msg_E_XQC_stock_out_event_failure, billNo, billType.label(), billEvent.label());
            }
        }
        String transactionKey = IXqcConst.getTransactionKey();
        _logger.info(String.format("库存增加事务启动: 事件[%s], 单据类型[%s], 单号[%s]", billEvent.label(), billType.label(), billNo));
        Map<ObjectId, Document> updateInventoryMap = new HashMap<>();
        Map<ObjectId, Document> updatedInventoryMap = new HashMap<>();
        try {
            if (!VtuzxUtil.isEmpty(boxNoList)) {
                Criteria updCond = Criteria.where("boxNo").in(boxNoList).and("stockNo").is(stockNo).and("status").is(StockStatus.In.toString());
                Iterator<Document> detailItr = inventoryDetailDao.findList(updCond, null, 0, 0);
                while (detailItr.hasNext()) {
                    Document detail = detailItr.next();
                    ObjectId _inventoryId = detail.getObjectId("_inventoryId");
                    ObjectId _id = detail.getObjectId("_id");
                    double count = VtuzxUtil.getDouble(detail, "count");
                    boolean isTaskIn = VtuzxUtil.getBoolean(detail, "isTaskIn");
                    List<Document> traceBackDocList = new ArrayList<>();
                    if (isTaskIn) {
                        List<Document> traceBack = VtuzxUtil.getObject(detail, "traceBack");
                        List<String> traceBackNoList = new ArrayList<>();
                        if (!VtuzxUtil.isEmpty(traceBack)) {
                            for (Document traceMap: traceBack) {
                                String traceBackNo = VtuzxUtil.getString(traceMap, "traceBackNo");
                                String status = VtuzxUtil.getString(traceMap, "status");
                                if ("in".equals(status)) {
                                    traceBackNoList.add(traceBackNo);
                                    Document data = new Document("traceBackNo", traceBackNo).append("status", "out");
                                    traceBackDocList.add(data);
                                } else {
                                    traceBackDocList.add(traceMap);
                                }
                            }
                        }
                        detail.append("traceBack", traceBackNoList);
                    }
                    Document inventory = updateInventoryMap.get(_inventoryId);
                    if (inventory == null) {
                        inventory = new Document("boxCount", -1).append("count", -count).append("boxList", new ArrayList<>());
                        updateInventoryMap.put(_inventoryId, inventory);
                    } else {
                        inventory.append("boxCount", VtuzxUtil.getInt(inventory, "boxCount") - 1).append("count", VtuzxUtil.getDouble(inventory, "count") - count);
                    }
                    List<Document> boxList = VtuzxUtil.getObject(inventory, "boxList");
                    boxList.add(detail);

                    Document updateDoc = new Document("status", StockStatus.Out.toString());
                    if (isTaskIn) {
                        updateDoc.append("traceBack", traceBackDocList);
                    }
                    inventoryDetailDao.updateById(_id, updateDoc);
                }
            }
            if (!VtuzxUtil.isEmpty(bcBoxList)) {
//                Criteria updCond = Criteria.where("boxNo").in(boxNoList).and("stockNo").is(stockNo).and("status").is(StockStatus.In.toString());
//                UpdateResult updRes = inventoryDetailDao.updateMany(updCond, new Document("status", StockStatus.Out.toString()));
//                if (updRes.getModifiedCount() != boxNoList.size()) {
//                    throw new VtuzxException(IXqcConst.Msg_E_XQC_stock_out_exist);
//                }
                List<String> bcBoxNoList = new ArrayList<>();
                List<String> traceBackList = new ArrayList<>();
                List<String> bcLocationNoList = new ArrayList<>();
                VtuzxMap bcBoxNoMap = new VtuzxMap();
                for (VtuzxMap bcBox : bcBoxList) {
                    String boxNo = VtuzxUtil.getString(bcBox, "boxNo");
                    String locationNo = VtuzxUtil.getString(bcBox, "locationNo");
                    double count = VtuzxUtil.getDouble(bcBox, "count");
                    List<String> traceBack = VtuzxUtil.getObject(bcBox, "traceBack");
                    if (!VtuzxUtil.isEmpty(traceBack)) {
                        traceBackList.addAll(traceBack);
                    }
                    bcBoxNoList.add(boxNo);
                    bcLocationNoList.add(locationNo);
                    bcBoxNoMap.append(boxNo, count);
                }
                Criteria cond = new Criteria("boxNo").in(bcBoxNoList).and("stockNo").is(stockNo).and("locationNo").in(bcLocationNoList);
                if (traceBackList.size() != 0) {
                    cond.and("traceBack.traceBackNo").in(traceBackList);
                }
                Iterator<Document> detailItr = inventoryDetailDao.findList(cond, null, 0, 0);
                while (detailItr.hasNext()) {
                    Document detail = detailItr.next();
                    ObjectId _id = VtuzxUtil.getObject(detail, IVtuzxConst.Key__id);
                    ObjectId _inventoryId = detail.getObjectId("_inventoryId");
                    List<Document> traceBackMapList = new ArrayList<>();
                    List<String> traceBackNoList = new ArrayList<>();
                    if (traceBackList.size() != 0) {
                        List<Document> traceBack = VtuzxUtil.getObject(detail, "traceBack");
                        for (Document traceMap: traceBack) {
                            String traceBackNo = VtuzxUtil.getString(traceMap, "traceBackNo");
                            if (traceBackList.contains(traceBackNo)) {
                                Document data = new Document("traceBackNo", traceBackNo).append("status", "out");
                                traceBackNoList.add(traceBackNo);
                                traceBackMapList.add(data);
                            } else {
                                traceBackMapList.add(traceMap);
                            }
                        }
                        detail.append("traceBack", traceBackNoList);
                    }
                    double count = VtuzxUtil.getDouble(bcBoxNoMap, VtuzxUtil.getString(detail, "boxNo"));
                    Document inventory = updateInventoryMap.get(_inventoryId);
                    if (inventory == null) {
                        inventory = new Document("count", -count).append("boxList", new ArrayList<>());
                        updateInventoryMap.put(_inventoryId, inventory);
                    } else {
                        inventory.append("count", VtuzxUtil.getDouble(inventory, "count") - count);
                    }
                    List<Document> boxList = VtuzxUtil.getObject(inventory, "boxList");
                    boxList.add(detail);
                    Document decDoc = new Document();
                    decDoc.append("count", -1 * count);
                    Document updateDoc = new Document("$inc", decDoc);
                    if (traceBackList.size() != 0) {
                        updateDoc.append("$set", new Document("traceBack", traceBackMapList));
                    }
                    inventoryDetailDao.updateByIdRedirect(_id, updateDoc);
                }
            }
            if (!VtuzxUtil.isEmpty(zsBoxList)) {
                VtuzxMap sysParamMap = getParameters(new VtuzxMap("keys", new VtuzxArrayList<>("traceabilityCodeUrl")));
                VtuzxMap traceabilityCodeUrlMap = VtuzxUtil.getObject(sysParamMap, "traceabilityCodeUrl");
                if (traceabilityCodeUrlMap == null) {
                    throw new VtuzxException(IXqcConst.Msg_E_system);
                }
                String value = VtuzxUtil.getString(traceabilityCodeUrlMap, "value");
                List<String> zsBoxStrList = new ArrayList<>();
                List<String> zsBoxHttpList = new ArrayList<>();
                VtuzxMap arrZsBoxMap = new VtuzxMap();
                for (Document zsBoxDoc: zsBoxList) {
                    String traceBack = VtuzxUtil.getString(zsBoxDoc, "traceBack");
                    String boxNo = VtuzxUtil.getString(zsBoxDoc, "boxNo");
                    List<String> traceBackList = VtuzxUtil.getObject(arrZsBoxMap, boxNo);
                    if (VtuzxUtil.isEmpty(traceBackList)) {
                        List<String> arrList = new ArrayList<>();
                        arrList.add(traceBack);
                        arrZsBoxMap.append(boxNo, arrList);
                    } else {
                        traceBackList.add(traceBack);
                    }
                    zsBoxHttpList.add(traceBack);
                    zsBoxStrList.add(traceBack.substring(traceBack.indexOf("Z")));
                }
                List<Document> invDetailDocList = new ArrayList<>();
                Criteria cond = new Criteria();
                cond.orOperator(Criteria.where("traceBack.traceBackNo").in(zsBoxHttpList), Criteria.where("traceBack.traceBackNo").in(zsBoxStrList)).and("status").is("in");
                Iterator<Document> inventoryDetailItr = inventoryDetailDao.findList(cond, null, 0, 0);
                while (inventoryDetailItr.hasNext()) {
                    Document inventoryDetailDoc = inventoryDetailItr.next();
                    invDetailDocList.add(inventoryDetailDoc);
                }


                arrZsBoxMap.forEach((k,v) -> {
                    int invSize = 0;
                    ObjectId id = null;
                    ObjectId inventoryId = null;
                    List<String> arrZsBoxList = (List<String>) v;
                    List<Document> updateTraceBackList = new ArrayList<>();
                    for (Document invDetailDoc: invDetailDocList) {
                        String boxNo = VtuzxUtil.getString(invDetailDoc, "boxNo");
                        List<Document> traceBack = VtuzxUtil.getObject(invDetailDoc, "traceBack");
                        if (k.equals(boxNo)) {
                            id = VtuzxUtil.getObject(invDetailDoc, "_id");
                            inventoryId = VtuzxUtil.getObject(invDetailDoc, "_inventoryId");
                            for (Document traceBackDoc: traceBack) {
                                String traceBackNo = VtuzxUtil.getString(traceBackDoc, "traceBackNo");
                                String traceBackNoStr = "";
                                if (traceBackNo.startsWith("Z")) {
                                    traceBackNoStr = value + "?traceabilityCode=" + traceBackNo;
                                } else {
                                    traceBackNoStr = traceBackNo;
                                }
                                String status = VtuzxUtil.getString(traceBackDoc, "status");
                                Document updateTraceBackDoc = new Document("traceBackNo", traceBackNo);
                                if (arrZsBoxList.contains(traceBackNoStr)) { //更新
                                    updateTraceBackDoc.append("status", "out");
                                } else {
                                    updateTraceBackDoc.append("status", status);
                                }
                                if ("in".equals(VtuzxUtil.getString(updateTraceBackDoc ,"status"))) {
                                    invSize++;
                                }
                                updateTraceBackList.add(updateTraceBackDoc);
                            }
                        }
                    }
                    Document inventoryDoc = inventoryDetailDao.findOne(Criteria.where("boxNo").is(k));
                    double traceCount = 0.0;
                    if (!VtuzxUtil.isEmpty(inventoryDoc)){
                        traceCount = VtuzxUtil.getDouble(inventoryDoc,"traceCount");
                    }

                    double sumCount = 0;//需要减库存的数量
                    if (!VtuzxUtil.isEmpty(traceCount)){
                        sumCount = traceCount * arrZsBoxList.size();
                    } else {//之前没有这个字段还是按1处理
                        sumCount = arrZsBoxList.size();
                    }
                    if (invSize >= 1) { //invSize = 1 是还有在库的

                        inventoryDetailDao.updateByIdRedirect(id, new Document("$set", new Document("count", -sumCount)).append("$set", new Document("traceBack", updateTraceBackList)));
                        inventoryDao.updateByIdRedirect(inventoryId, new Document("$set", new Document("count", -sumCount)));
                    } else {
                        inventoryDetailDao.updateByIdRedirect(id, new Document("$set", new Document("count", -sumCount)).append("$set", new Document("status", "out").append("traceBack", updateTraceBackList)));
                        inventoryDao.updateByIdRedirect(inventoryId, new Document("$set", new Document("count", -sumCount).append("boxCount", -1)));
                    }
                });
            }
            for (ObjectId _inventoryId : updateInventoryMap.keySet()) {
                reduceInventoryMaterial(_userId, userName, transactionKey, stockNo, billEvent, billType, billNo, _inventoryId, updateInventoryMap.get(_inventoryId), updatedInventoryMap);
            }
            _logger.info(String.format("库存增加事务完成: 事件[%s], 单据类型[%s], 单号[%s]", billEvent.label(), billType.label(), billNo));
        } catch (Exception e) {
            _logger.warn(String.format("库存增加异常: 事件[%s], 单据类型[%s], 单号[%s], %s", billEvent.label(), billType.label(), billNo, e.getMessage()), e);
            if (!VtuzxUtil.isEmpty(boxNoList)) {
                Criteria updCond = Criteria.where("boxNo").in(boxNoList).and("stockNo").is(stockNo);
                inventoryDetailDao.updateMany(updCond, new Document("status", StockStatus.In.toString()));
            }
            Criteria rollbackCond = Criteria.where("transactionKey").is(transactionKey);
            inventoryHistoryDao.deleteMany(rollbackCond);
            inventoryHistoryDetailDao.deleteMany(rollbackCond);
            for (ObjectId _updateId : updatedInventoryMap.keySet()) {
                Document incDoc = updatedInventoryMap.get(_updateId);
                incDoc.append("boxCount", -1 * VtuzxUtil.getInt(incDoc, "boxCount"));
                incDoc.append("count", -1 * VtuzxUtil.getDouble(incDoc, "count"));
                inventoryDao.updateByIdRedirect(_updateId, new Document("$inc", incDoc));
                Document decDoc = new Document();
                decDoc.append("count", VtuzxUtil.getDouble(incDoc, "count"));
                inventoryDetailDao.updateOneRedirect(Criteria.where("_inventoryId").is(_updateId), new Document("$inc", decDoc));
            }
            _logger.info(String.format("库存增加回滚完成: 事件[%s], 单据类型[%s], 单号[%s]", billEvent.label(), billType.label(), billNo));
            throw e;
        }
    }



    public VtuzxMap getParameters(Map<String, Object> param) {
        List<String> keys = VtuzxUtil.getObject(param, "keys");
        Iterator<Document> docList = CoreParamDao.findList(Criteria.where("key").in(keys).and("effect").is(true), null, 0, 0);
        VtuzxMap retMap = new VtuzxMap();
        while(docList.hasNext()) {
            Document doc = docList.next();
            VtuzxMap data = new VtuzxMap();
            String key = VtuzxUtil.getString(doc, "key");
            data.put("key", key);
            data.put("name", VtuzxUtil.getString(doc, "name"));
            data.put("value", VtuzxUtil.getString(doc, "value"));
            retMap.put(key, data);
        }

        return retMap;
    }

    @Override
    public void addInventory(ObjectId _userId, String userName, BillEvent billEvent, BillType billType, String stockNo, String billNo, List<BillMaterial> billMaterialList) throws VtuzxException {
        _logger.info(String.format("即时库存入库开始: 事件[%s], 单据类型[%s], 单号[%s]", billEvent, billType.label(), billNo));
        int sign = billType.inventoryIO();
        if (sign == 0) {
            throw new VtuzxException(IXqcConst.Msg_E_XQC_stock_bill_failure, billNo, billType.label(), billEvent.label());
        }
        switch (billEvent) {
            case Audit: {
                if (sign < 0) {
                    throw new VtuzxException(IXqcConst.Msg_E_XQC_stock_event_failure, billNo, billType.label(), billEvent.label());
                }
                break;
            }
            case Unaudit: {
                if (sign > 0) {
                    throw new VtuzxException(IXqcConst.Msg_E_XQC_stock_event_failure, billNo, billType.label(), billEvent.label());
                }
                break;
            }
            default: {
                throw new VtuzxException(IXqcConst.Msg_E_XQC_stock_event_failure, billNo, billType.label(), billEvent.label());
            }
        }
        String transactionKey = IXqcConst.getTransactionKey();
        _logger.info(String.format("即时库存入库事务启动: 事件[%s], 单据类型[%s], 单号[%s]", billEvent.label(), billType.label(), billNo));
        Map<ObjectId, Document> updateInventoryMap = new HashMap<>();
        try {
            for (BillMaterial material : billMaterialList) {
                _logger.info(String.format("物料即时库存入库: 单号[%s], 物料[%s]", billNo, material.getMaterialNo()));
                addInventoryMaterial(_userId, userName, transactionKey, stockNo, billEvent, billType, billNo, material, updateInventoryMap);
                _logger.info(String.format("物料即时库存入库: 单号[%s], 物料[%s]", billNo, material.getMaterialNo()));
            }
            _logger.info(String.format("即时库存入库事务完成: 事件[%s], 单据类型[%s], 单号[%s]", billEvent.label(), billType.label(), billNo));
        } catch (Exception e) {
            _logger.warn(String.format("即时库存入库异常: 事件[%s], 单据类型[%s], 单号[%s], %s", billEvent.label(), billType.label(), billNo, e.getMessage()), e);
            for (ObjectId _updateId : updateInventoryMap.keySet()) {
                Document incDoc = updateInventoryMap.get(_updateId);
                incDoc.append("boxCount", -1 * VtuzxUtil.getInt(incDoc, "boxCount"));
                incDoc.append("count", -1 * VtuzxUtil.getDouble(incDoc, "count"));
                inventoryDao.updateByIdRedirect(_updateId, new Document("$inc", incDoc));
            }
            Criteria rollbackCond = Criteria.where("transactionKey").is(transactionKey);
            inventoryDao.deleteMany(rollbackCond);
            inventoryHistoryDao.deleteMany(rollbackCond);
            inventoryDetailDao.deleteMany(rollbackCond);
            inventoryHistoryDetailDao.deleteMany(rollbackCond);
            _logger.info(String.format("即时库存入库回滚完成: 事件[%s], 单据类型[%s], 单号[%s]", billEvent.label(), billType.label(), billNo));
            throw e;
        }
    }
    /**
     * 保存库存物料
     *
     * @param _userId            用户ID
     * @param userName           用户名
     * @param stockNo            仓库编码
     * @param event              单据动作
     * @param billType           单据类型
     * @param billNo             单据号
     * @param material           物料信息
     * @param updateInventoryMap 更新对象Map, 记录并用于回滚
     */
    private void addInventoryMaterial(ObjectId _userId, String userName,
                                      String transactionKey, String stockNo, BillEvent event, BillType billType, String billNo,
                                      BillMaterial material, Map<ObjectId, Document> updateInventoryMap) {
        // 查询库存当前物料
        Criteria cond = Criteria.where("stockNo").is(stockNo)
                .and("materialNo").is(material.getMaterialNo())
                .and("produceDate").is(material.getProduceDate())
                .and("exp").is(material.getExp())
                .and("batchNo").is(material.getBatchNo());

        Document inventoryDoc = inventoryDao.findOne(cond);

        List<BillMaterialBox> boxList = material.getBoxList();
        double total = 0;
        for (BillMaterialBox box : boxList) {
            total += box.getCount();
        }
        int boxCount = boxList.size();
        List<String> baocaiBoxNoList = new ArrayList<>();
        for (BillMaterialBox box : boxList) {
            String boxNo = box.getBoxNo();
            if (boxNo.contains("|;")) {
                // 是包材
                baocaiBoxNoList.add(boxNo);
            }
        }

        Document inventoryHistory;
        ObjectId _inventoryId;
        if (inventoryDoc == null) {
            inventoryDoc = new Document("transactionKey", transactionKey)
                    .append("stockNo", stockNo)
                    .append("materialNo", material.getMaterialNo())
                    .append("produceDate", material.getProduceDate())
                    .append("exp", material.getExp())
                    .append("batchNo", material.getBatchNo())
                    .append("unit", material.getUnit())
                    .append("boxCount", boxCount)
                    .append("count", total);
            inventoryHistory = new Document(inventoryDoc).append("billNo", billNo).append("billType", billType.toString()).append("billEvent", event.toString());
            _inventoryId = inventoryDao.insertStock(_userId, userName, inventoryDoc);
        } else {
            _inventoryId = inventoryDoc.getObjectId(IVtuzxConst.Key__id);
            Document incDoc = new Document("boxCount", boxCount - baocaiBoxNoList.size()).append("count", total);
            inventoryDao.updateStock(_userId, userName, _inventoryId, incDoc);
            updateInventoryMap.put(_inventoryId, incDoc);
            inventoryHistory = new Document("transactionKey", transactionKey)
                    .append("billNo", billNo)
                    .append("stockNo", stockNo)
                    .append("materialNo", material.getMaterialNo())
                    .append("produceDate", material.getProduceDate())
                    .append("exp", material.getExp())
                    .append("batchNo", material.getBatchNo())
                    .append("unit", material.getUnit())
                    .append("boxCount", boxCount)
                    .append("count", total);
        }
        inventoryHistory.append("_inventoryId", _inventoryId);
        ObjectId _historyId = inventoryHistoryDao.insertHistory(_userId, userName, inventoryHistory);

        VtuzxMap baocaiMap = new VtuzxMap();
        if (baocaiBoxNoList.size() > 0) {
            Iterator<Document> existsBoxItr = inventoryDetailDao.findList(Criteria.where("boxNo").in(baocaiBoxNoList), null, 0, 0);
            while(existsBoxItr.hasNext()) {
                Document box = existsBoxItr.next();
                String boxNo = VtuzxUtil.getString(box, "boxNo");
                String locationNo = VtuzxUtil.getString(box, "locationNo");
                baocaiMap.append(boxNo + locationNo, box);
            }
        }

        List<Document> detailDocList = new ArrayList<>();
        List<Document> historyDetailDocList = new ArrayList<>();
        for (BillMaterialBox box : boxList) {
            String boxNo = box.getBoxNo();
            String locationNo = box.getLocationNo();
            List<Document> traceBack = box.getTraceBack();
            double count = box.getCount();
            Document detailDoc = new Document("transactionKey", transactionKey)
                    .append("boxNo", box.getBoxNo())
                    .append("rfid", box.getRfid())
                    .append("count", box.getCount())
                    .append("traceBack", box.getTraceBack())
                    .append("trayNumber", box.getTrayNumber())
                    .append("isTaskIn", box.getIsTaskIn())
                    .append("stockNo", stockNo)
                    .append("locationNo", box.getLocationNo())
                    .append("status", StockStatus.In.toString())
                    //.append("traceCount",count / traceBack.size())//追溯码重量
                    .append("_inventoryId", _inventoryId);
            if (!VtuzxUtil.isEmpty(traceBack)){
                detailDoc.append("traceCount",count / traceBack.size());
            }

            Document historyDetailDoc = new Document(detailDoc)
                    .append("billNo", billNo)
                    .append("billType", billType.toString())
                    .append("billEvent", event.toString())
                    .append("_historyId", _historyId);

            if (baocaiMap.containsKey(boxNo + locationNo)) {
                Document baocaiBox = VtuzxUtil.getObject(baocaiMap, boxNo + locationNo);
                ObjectId _baocaiId = VtuzxUtil.getObject(baocaiBox, IVtuzxConst.Key__id);
                Document incDoc = new Document();
                incDoc.append("count", BigDecimal.valueOf(box.getCount()).intValue());
                inventoryDetailDao.updateByIdRedirect(_baocaiId, new Document("$inc", incDoc));
            } else {
                detailDocList.add(detailDoc);
            }
            historyDetailDocList.add(historyDetailDoc);
        }
        if (!VtuzxUtil.isEmpty(detailDocList)) {
            inventoryDetailDao.insertDetail(_userId, userName, detailDocList);
        }
        if (!VtuzxUtil.isEmpty(historyDetailDocList)) {
            inventoryHistoryDetailDao.insertDetail(_userId, userName, historyDetailDocList);
        }
    }
}
